// Copyright 2017 Google LLC. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//    http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package main

import (
	"fmt"
	"strings"

	"github.com/googleapis/gnostic/printer"
)

// ProtoOption represents an option to be added to generated .proto files.
type ProtoOption struct {
	Name    string
	Value   string
	Comment string
}

// generateProto produces the contents of a .proto file corresponding to the domain.
func (domain *Domain) generateProto(packageName string, license string, options []ProtoOption, imports []string) string {
	code := &printer.Code{}
	code.Print(license)
	code.Print("// THIS FILE IS AUTOMATICALLY GENERATED.")
	code.Print()
	code.Print("syntax = \"proto3\";")
	code.Print()
	code.Print("package " + packageName + ";")
	for _, importString := range imports {
		code.Print()
		code.Print("import \"" + importString + "\";")
	}
	code.Print()

	// generate option declarations
	for _, option := range options {
		domain.generateOptionDeclaration(code, option)
	}

	// generate message definitions
	typeNames := domain.sortedTypeNames()
	for _, typeName := range typeNames {
		domain.generateProtoMessage(code, typeName)
	}

	return code.String()
}

func (domain *Domain) generateOptionDeclaration(code *printer.Code, option ProtoOption) {
	commentLines := strings.Split(option.Comment, "\n")
	for _, commentLine := range commentLines {
		code.Print(commentLine)
	}
	line := "option " + option.Name + " = "
	if option.Value == "true" || option.Value == "false" {
		line += option.Value
	} else {
		line += "\"" + option.Value + "\""
	}
	line += ";\n"
	code.Print(line)
}

func (domain *Domain) generateProtoMessage(code *printer.Code, typeName string) {
	typeModel := domain.TypeModels[typeName]
	if typeModel.Description != "" {
		code.Print("// %s", typeModel.Description)
	}
	code.Print("message %s {", typeName)
	code.Indent()
	if typeModel.OneOfWrapper {
		code.Print("oneof oneof {")
		code.Indent()
	}
	var fieldNumber = 0
	for _, propertyModel := range typeModel.Properties {
		// print a leading comment if available
		if propertyModel.Description != "" {
			code.Print("// %s", propertyModel.Description)
		}
		// adjust the property type to a valid type name
		propertyType := propertyModel.Type
		if propertyType == "int" {
			propertyType = "int64"
		}
		if propertyType == "float" {
			propertyType = "double"
		}
		if propertyType == "blob" {
			propertyType = "string"
		}
		// adjust the display name to a valid identifier
		propertyName := propertyModel.Name
		var displayName = propertyName
		if displayName == "$ref" {
			displayName = "_ref"
		}
		if displayName == "$schema" {
			displayName = "_schema"
		}
		displayName = camelCaseToSnakeCase(displayName)
		// assign a field number to the property
		fieldNumber++
		// print the field declaration
		var line = fmt.Sprintf("%s %s = %d;", propertyType, displayName, fieldNumber)
		if propertyModel.Repeated {
			line = "repeated " + line
		}
		code.Print(line)
	}
	if typeModel.OneOfWrapper {
		code.Outdent()
		code.Print("}")
	}
	code.Outdent()
	code.Print("}")
	code.Print()
}
